﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Reflection;
using System.Linq;
using System.Windows.Threading;
using System.Diagnostics;
using Utils;

namespace Hydra.Shell.Exceptions
{
    public abstract class ObjectNotYetSitedException : Exception
    {
        protected object objectToSite;
        protected string objectTypeName;
        protected MemberInfo getSiteMember;
        public event EventHandler<EventArgs<Exception>> OnException;
        public event EventHandler<EventArgs<Hydra.Shell.Interfaces.IServiceProvider>> OnSited;

        public void WatchForSited(TimeSpan checkInterval, TimeSpan timeOut)
        {
            var timer = new DispatcherTimer();
            var startTime = DateTime.Now;

            timer.Interval = checkInterval;

            timer.Tick += (sender, e) =>
            {
                try
                {
                    var site = GetSite();

                    if (site != null)
                    {
                        OnSited(this, new EventArgs<Interfaces.IServiceProvider>(site));
                        timer.Stop();
                    }
                }
                catch (Exception ex)
                {
                    OnException(this, new EventArgs<Exception>(ex));
                    timer.Stop();
                }

                if (DateTime.Now - startTime > timeOut)
                {
                    OnException(this, new EventArgs<Exception>(new TimeoutException(string.Format("Error waiting for object of type {0} to be sited", objectTypeName))));
                    timer.Stop();
                }
            };

            timer.Start();
        }

        private Hydra.Shell.Interfaces.IServiceProvider GetSite()
        {
            Hydra.Shell.Interfaces.IServiceProvider serviceProvider = null;

            if (getSiteMember is MethodInfo)
            {
                var getSiteMethod = (MethodInfo)getSiteMember;

                try
                {
                    serviceProvider = (Hydra.Shell.Interfaces.IServiceProvider)getSiteMethod.Invoke(objectToSite, new object[0]);
                }
                catch (TargetInvocationException ex)
                {
                    if (ex.InnerException is ObjectNotYetSitedException)
                    {
                        return null;
                    }
                    else
                    {
                        throw;
                    }
                }
            }
            else if (getSiteMember is PropertyInfo)
            {
                var getSiteProperty = (PropertyInfo)getSiteMember;

                try
                {
                    serviceProvider = (Hydra.Shell.Interfaces.IServiceProvider)getSiteProperty.GetGetMethod().Invoke(objectToSite, new object[0]);
                }
                catch (TargetInvocationException ex)
                {
                    if (ex.InnerException is ObjectNotYetSitedException)
                    {
                        return null;
                    }
                    else
                    {
                        throw;
                    }
                }
            }
            else
            {
                Debugger.Break();
            }

            return serviceProvider;
        }

        public override string Message
        {
            get
            {
                return string.Format("Object of type '{0}' not yet sited", objectTypeName);
            }
        }

    }

    public class ObjectNotYetSitedException<T> : ObjectNotYetSitedException
    {
        public ObjectNotYetSitedException(T objectToSite, string getSiteMemberName)
        {
            this.objectToSite = objectToSite;
            objectTypeName = objectToSite.GetType().Name;

            this.getSiteMember = typeof(T).GetMember(getSiteMemberName).First();
        }
    }
}
